/*
 * Copyright (c) 2020-2024. Devtron Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pipeline

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1"
	pubsub "github.com/devtron-labs/common-lib/pubsub-lib"
	"github.com/devtron-labs/common-lib/utils/registry"
	"github.com/devtron-labs/devtron/internal/sql/repository"
	"github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig"
	"github.com/devtron-labs/devtron/pkg/pipeline/bean"
	types2 "github.com/devtron-labs/devtron/pkg/pipeline/types"
	"github.com/devtron-labs/devtron/pkg/pipeline/workflowStatus"
	"github.com/devtron-labs/devtron/pkg/workflow/cd"
	util3 "github.com/devtron-labs/devtron/util"
	"go.uber.org/zap"
	"strconv"
	"strings"
	"time"
)

// this object's current object was previously used as CiCompleteEvent, duplicating it currently to remove unused fields here
type ExternalCiWebhookDto struct {
	CiProjectDetails              []bean.CiProjectDetails `json:"ciProjectDetails"`
	DockerImage                   string                  `json:"dockerImage" validate:"required,image-validator"`
	Digest                        string                  `json:"digest"`
	PipelineId                    int                     `json:"pipelineId"`
	WorkflowId                    *int                    `json:"workflowId"`
	TriggeredBy                   int32                   `json:"triggeredBy"`
	PipelineName                  string                  `json:"pipelineName"`
	DataSource                    string                  `json:"dataSource"`
	MaterialType                  string                  `json:"materialType"`
	Metrics                       util3.CIMetrics         `json:"metrics"`
	AppName                       string                  `json:"appName"`
	IsArtifactUploaded            bool                    `json:"isArtifactUploaded"`
	FailureReason                 string                  `json:"failureReason"`
	ImageDetailsFromCR            json.RawMessage         `json:"imageDetailsFromCR"`
	PluginRegistryArtifactDetails map[string][]string     `json:"PluginRegistryArtifactDetails"`
	PluginArtifactStage           string                  `json:"pluginArtifactStage"`
}

type CiArtifactWebhookRequest struct {
	Image                         string                         `json:"image" validate:"required"`
	ImageDigest                   string                         `json:"imageDigest"`
	MaterialInfo                  json.RawMessage                `json:"materialInfo"`
	DataSource                    repository.ArtifactsSourceType `json:"dataSource" validate:"oneof=CI-RUNNER EXTERNAL pre_cd post_cd post_ci GOCD"`
	PipelineName                  string                         `json:"pipelineName"`
	WorkflowId                    *int                           `json:"workflowId"`
	UserId                        int32                          `json:"userId"`
	IsArtifactUploaded            bool                           `json:"isArtifactUploaded"`
	FailureReason                 string                         `json:"failureReason"`
	PluginRegistryArtifactDetails map[string][]string            `json:"PluginRegistryArtifactDetails"` //map of registry and array of images generated by Copy container image plugin
	PluginArtifactStage           string                         `json:"pluginArtifactStage"`           // at which stage of CI artifact was generated by plugin ("pre_ci/post_ci")
}

type WebhookService interface {
	AuthenticateExternalCiWebhook(apiKey string) (int, error)
	HandleMultipleImagesFromEvent(imageDetails []*registry.GenericImageDetail, ciWorkflowId int) (map[string]*pipelineConfig.CiWorkflow, error)
	GetTriggerValidateFuncs() []pubsub.ValidateMsg
}

type WebhookServiceImpl struct {
	ciArtifactRepository       repository.CiArtifactRepository
	ciConfig                   *types2.CiConfig
	logger                     *zap.SugaredLogger
	ciPipelineRepository       pipelineConfig.CiPipelineRepository
	ciWorkflowRepository       pipelineConfig.CiWorkflowRepository
	cdWorkflowCommonService    cd.CdWorkflowCommonService
	workFlowStageStatusService workflowStatus.WorkFlowStageStatusService
	ciService                  CiService
}

func NewWebhookServiceImpl(ciArtifactRepository repository.CiArtifactRepository,
	logger *zap.SugaredLogger, ciPipelineRepository pipelineConfig.CiPipelineRepository,
	ciWorkflowRepository pipelineConfig.CiWorkflowRepository,
	cdWorkflowCommonService cd.CdWorkflowCommonService,
	workFlowStageStatusService workflowStatus.WorkFlowStageStatusService,
	ciService CiService) *WebhookServiceImpl {
	webhookHandler := &WebhookServiceImpl{
		ciArtifactRepository:       ciArtifactRepository,
		logger:                     logger,
		ciPipelineRepository:       ciPipelineRepository,
		ciWorkflowRepository:       ciWorkflowRepository,
		cdWorkflowCommonService:    cdWorkflowCommonService,
		workFlowStageStatusService: workFlowStageStatusService,
		ciService:                  ciService,
	}
	config, err := types2.GetCiConfig()
	if err != nil {
		return nil
	}
	webhookHandler.ciConfig = config
	return webhookHandler
}

func (impl WebhookServiceImpl) AuthenticateExternalCiWebhook(apiKey string) (int, error) {
	impl.logger.Debug("external ci webhook auth")
	splitKey := strings.Split(apiKey, ".")

	if len(splitKey) != 2 {
		return 0, fmt.Errorf("invalid key")
	}

	encodedCiPipelineId := splitKey[0]
	sha := splitKey[1]

	ciPipelineId, err := base64.StdEncoding.DecodeString(encodedCiPipelineId)
	if err != nil {
		impl.logger.Errorw("err", "err", err)
		return 0, fmt.Errorf("invalid ci pipeline")
	}
	id, err := strconv.Atoi(string(ciPipelineId))
	if err != nil {
		impl.logger.Errorw("err", "err", err)
		return 0, fmt.Errorf("invalid ci pipeline")
	}
	externalCiPipeline, err := impl.ciPipelineRepository.FindExternalCiByCiPipelineId(id)
	if externalCiPipeline.AccessToken != sha {
		return 0, fmt.Errorf("invalid key, auth failed")
	}
	return id, nil
}

// HandleMultipleImagesFromEvent handles multiple images from plugin and creates ci workflow for n-1 images for mapping in ci_artifact
func (impl *WebhookServiceImpl) HandleMultipleImagesFromEvent(imageDetails []*registry.GenericImageDetail, ciWorkflowId int) (map[string]*pipelineConfig.CiWorkflow, error) {
	ciWorkflow, err := impl.ciWorkflowRepository.FindById(ciWorkflowId)
	if err != nil {
		impl.logger.Errorw("error in finding ci workflow by id ", "err", err, "ciWorkFlowId", ciWorkflowId)
		return nil, err
	}

	// creating n-1 workflows for rest images, oldest will be mapped to original workflow id.
	digestWorkflowMap := make(map[string]*pipelineConfig.CiWorkflow)
	// mapping oldest to original ciworkflowId
	digestWorkflowMap[imageDetails[0].GetGenericImageDetailIdentifier()] = ciWorkflow
	for i := 1; i < len(imageDetails); i++ {
		workflow := &pipelineConfig.CiWorkflow{
			Name:               ciWorkflow.Name + fmt.Sprintf("-child-%d", i),
			Status:             ciWorkflow.Status,
			PodStatus:          string(v1alpha1.NodeSucceeded),
			StartedOn:          time.Now(),
			Namespace:          ciWorkflow.Namespace,
			LogLocation:        ciWorkflow.LogLocation,
			TriggeredBy:        ciWorkflow.TriggeredBy,
			CiPipelineId:       ciWorkflow.CiPipelineId,
			CiArtifactLocation: ciWorkflow.CiArtifactLocation,
			BlobStorageEnabled: ciWorkflow.BlobStorageEnabled,
			PodName:            ciWorkflow.PodName,
			CiBuildType:        ciWorkflow.CiBuildType,
			ParentCiWorkFlowId: ciWorkflow.Id,
			GitTriggers:        ciWorkflow.GitTriggers,
			Message:            ciWorkflow.Message,
		}
		err = impl.ciService.SaveCiWorkflowWithStage(workflow)
		if err != nil {
			impl.logger.Errorw("error in saving workflow for child workflow", "err", err, "parentCiWorkflowId", ciWorkflowId)
			return nil, err
		}
		digestWorkflowMap[imageDetails[i].GetGenericImageDetailIdentifier()] = workflow

	}
	return digestWorkflowMap, nil
}

func (impl *WebhookServiceImpl) GetTriggerValidateFuncs() []pubsub.ValidateMsg {
	return impl.cdWorkflowCommonService.GetTriggerValidateFuncs()
}
